﻿namespace LogManager.Utils.ChangeTracking
{
    using LogManager.Utils.Helpers;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    public abstract class DependPropertyChangeBase : PropertyChangeBase
    {
        private readonly IDictionary<string, List<string>> mPropertyMap;

        [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true)]
        protected class DependsUponAttribute : Attribute
        {
            public string DependencyName { get; private set; }

            public DependsUponAttribute(string propertyName)
            {
                DependencyName = propertyName;
            }
        }

        public DependPropertyChangeBase()
        {
            mPropertyMap = MapDependencies<DependsUponAttribute>(() => GetType().GetProperties());

            VerifyDependancies();
        }

        public override void RaisePropertyChanged(string propertyName)
        {
            base.RaisePropertyChanged(propertyName);

            if (mPropertyMap.ContainsKey(propertyName))
            {
                mPropertyMap[propertyName].ForEach(RaisePropertyChanged);
            }
        }

        protected static IDictionary<string, List<string>> MapDependencies<T>(Func<IEnumerable<MemberInfo>> getInfo) where T : DependsUponAttribute
        {
            var uniqueList = getInfo().GroupBy(p => p.Name).Select(g => g.First());
            var dependencyMap = uniqueList.ToDictionary(
                        p => p.Name,
                        p => p.GetCustomAttributes(typeof(T), true)
                                .Cast<T>()
                                .Select(a => a.DependencyName)
                                .ToList());

            return Invert(dependencyMap);
        }

        private static IDictionary<string, List<string>> Invert(IDictionary<string, List<string>> map)
        {
            var flattened = from key in map.Keys
                            from value in map[key]
                            select new { Key = key, Value = value };

            var uniqueValues = flattened.ToList().Select(x => x.Value).Distinct();

            return uniqueValues.ToDictionary(
                        x => x,
                        x => (from item in flattened
                              where item.Value == x
                              select item.Key).ToList());
        }

        private void VerifyDependancies()
        {
            var propertyNames = GetType().GetProperties()
            .SelectMany(method => method.GetCustomAttributes(typeof(DependsUponAttribute), true).Cast<DependsUponAttribute>())
            .Select(attribute => attribute.DependencyName);

            propertyNames.ForEach(VerifyDependancy);
        }

        private void VerifyDependancy(string propertyName)
        {
            var property = GetType().GetProperty(propertyName);
            if (property == null)
            {
                throw new ArgumentException("DependsUpon Property Does Not Exist: " + propertyName);
            }
        }
    }
}

